package com.visionarysoftwaresolutions

class Realization implements Comparable {
    static belongsTo = [vision:Vision]

    static hasMany = [
                      goals:Goal,
                      children:Realization,
                      risks:Risk
                     ]

    String name
    String description
    Date start
    Date estimatedEnd
    Date achievedEnd
    Date originalStart
    Date originalEstimatedEnd
    String status
    String value
    String comments
    Date lastUpdated
    Date dateCreated
    SortedSet children

    static constraints = {
        name(blank:false)
        description(nullable:true, maxSize:1000)
        start(validator: { begin, milestone ->
            begin <= milestone.estimatedEnd
        })
        estimatedEnd(validator: { end, milestone ->
                end >= milestone.start
        })
        achievedEnd(nullable:true, editable:false)
        status(inList:["Not Started", "Behind", "On Track", "Ahead", "Done",
                       "Suspended", "Canceled", "Replaced"],
               validator: { stat, milestone ->
                   !(stat =="On Track" && milestone.estimatedEnd < (new Date()-1))
               }
        )
        value(inList:["low", "medium", "high"])
        comments(nullable:true)
        originalStart(nullable:true)
        originalEstimatedEnd(nullable:true)
    }

    int compareTo(obj) {
       return (start) ? ((obj && obj.start) ? start.compareTo(obj.start) : 1) : -1
   }

    @Override
    public String toString() { return name }


    public void setStart(Date newStart)
    {
        start= newStart
        if(originalStart == null)
        {
            originalStart = start
        }
    }

        /**
     *setStatus() -- overriden method to implement internal
     *requirement that when a milestone's status becomes complete, the date
     *is made the end date
     *added requirement that when a milestone falls behind, all its children
     *fall behind
     */
    public void setStatus(String newStatus)
    {
        status = newStatus
        if(isComplete() && achievedEnd == null)
        {
            achievedEnd = new Date()
        }

        if(status == "Behind" && children?.size() > 0)
        {
            children.each { if(!it.isComplete()) it.status = "Behind"}
        }
    }
    /**
     *setEstimatedEnd() -- overriden method to implement internal
     *requirement that when a milestone's end date is updated,
     *that update is propagated to the children and save the original
     *estimated end to the domain object
     */
    public void setEstimatedEnd(Date newEstimate)
    {
        updateChildEstimates(estimatedEnd, newEstimate)
        estimatedEnd = newEstimate
        if(originalEstimatedEnd == null)
        {
            originalEstimatedEnd = estimatedEnd
        }
    }

    public Boolean isInProgress()
    {
        return status.equals("Behind") ||
               status.equals("On Track") ||
               status.equals("Ahead")
    }

    public Boolean isAtRisk()
    {
        return status.equals("Behind") || status.equals("Suspended")
    }

    public Boolean isComplete()
    {
        return status.equals("Done")  ||
               status.equals("Canceled") || status.equals("Replaced")
    }

    private void updateChildEstimates(Date oldEstimate, Date newEstimate)
    {
        if(oldEstimate != null && children?.size() > 0)
        {
            def difference = newEstimate - oldEstimate
            //if it gets pulled, then it is negative
            //if it gets pushed, then it is positive
            children.each{
                    if(it.start != null)
                    {
                        it.start +=  difference
                    }

                if(it.estimatedEnd != null)
                {
                    it.estimatedEnd += difference
                }
            }
        }
    }

    /**
    * beforeUpdate() -- hooks into Grails update via
    * http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.5.1%20Events%20and%20Auto%20Timestamping
    * so that when a milestone is updated, all nested milestones are updated
    */
     def beforeUpdate()
    {
        // if the estimated end is yesterday or before and the project is still
        // in progress, set the status to behind
        boolean pastEstimate = estimatedEnd < (new Date()-1)
        if(this.isDirty() &&
            this.isInProgress() && pastEstimate)
        {
            this.status = "Behind"
        }
    }
}
